#ifndef _OBJECT_H_
#define _OBJECT_H_

#include <memory>
#include <vector>
#include <unordered_map>
#include <functional>
#include "Component.h"

namespace Engine2D
{

    class IObject : public std::enable_shared_from_this<IObject>
    {
    public:
        enum ForeachEnum 
        {
            Break,
            Continue,
        };
    public:
        virtual ~IObject() = default;
        // virtual std::shared_ptr<IObject> Clone() = 0;

        std::shared_ptr<IObject> SetParent(std::shared_ptr<IObject> parent) { parent->AddChild(this->shared_from_this()); return parent; }

        std::shared_ptr<IObject> AddChild(std::shared_ptr<IObject> child) {
            m_children.push_back(child);
            child->m_parent = this->shared_from_this();
            return child;
        }

        template<typename T, typename... Args>
        void AddComponent(Args&&... args)
        {
            m_components[typeid(T).hash_code()] = std::make_shared<T>(std::forward<Args>(args)...);
            m_components[typeid(T).hash_code()]->OnInit(this);
        }

        template<typename T>
        bool RemoveComponent()
        {
            if (m_components.find(typeid(T).hash_code()) == m_components.end()) {
                return false;
            }
            m_components.erase(typeid(T).hash_code());
            return true;
        }

        template<typename T>
        std::weak_ptr<T> GetComponent()
        {
            if (m_components.find(typeid(T).hash_code()) == m_components.end()) {
                return std::weak_ptr<T>();
            }
            return std::dynamic_pointer_cast<T>(m_components[typeid(T).hash_code()]);
        }

        void OnTick() 
        {
            for (auto && child : m_children) {
                child->OnTick();
            }
            for (auto && comp : m_components) {
                comp.second->OnTick(this);
            }
        }

        template<typename T>
        void Subscribe(std::shared_ptr<Component> comp) 
        {
            m_registerTable[typeid(T).hash_code()].push_back(comp);
        }

        template<typename T>
        void Send(T & msg)
        {
            for (auto && comp : m_registerTable[typeid(T).hash_code()]) {
                if (!comp.expired()) {
                    comp.lock()->OnMessage(&msg);
                }
            }
        }

        template<typename F, typename Component, typename... Components>
        void ForeachComponents(F && fn, ComponentGroup<Component, Components...>)
        {
            if (m_components.find(typeid(Component).hash_code()) != m_components.end()) {
                fn(*std::dynamic_pointer_cast<Component>(m_components[typeid(Component).hash_code()]));
            }
            if constexpr (sizeof...(Components) != 0) {
                ComponentGroup<Components...> group;
                ForeachComponents(std::forward<F>(fn), group);
            }
        }

        template<typename... Components>
        void Foreach(std::function<ForeachEnum(std::shared_ptr<IObject>)> fn, bool isRecurse = true)
        {
            for (auto & item : m_children) {
                if (IsAllTrue((!this->GetComponent<Components>().expired())...)) {
                    if (fn(item) == ForeachEnum::Break) {
                        break;
                    }
                } 
                if (isRecurse) {
                    item->Foreach<Components...>(fn);
                }
            }
        }

        void Foreach(std::function<ForeachEnum(std::shared_ptr<IObject>)> fn, bool isRecurse = true)
        {
            for (auto & item : m_children) {
                if (fn(item) == ForeachEnum::Break) {
                    break;
                }
                if (isRecurse) {
                    item->Foreach(fn);
                }
            }
        }

        bool RemoveChild(std::shared_ptr<IObject> obj) {
            for (auto && child : m_children) {
                if (child == obj) {
                    m_children.erase(std::remove_if(m_children.begin(), m_children.end(), [obj](auto item) { return obj == item; }), m_children.end());
                    return true;
                }
                if (child->RemoveChild(obj)) {
                    return true;
                }
            }
            return false;
        }

    protected:
        std::weak_ptr<IObject> m_parent;
        std::vector<std::shared_ptr<IObject>> m_children;
        std::unordered_map<size_t, std::shared_ptr<Component>> m_components;
        std::unordered_map<size_t, std::vector<std::weak_ptr<Component>>> m_registerTable;
    };

} // namespace Engine2D



#endif